一、概述
JNI(Java Native Interface,Java本地接口),是连接Android Native层和Java层的纽带,这个是Java所特有的,并不是Android系统独有。Java作为跨平台的语言,依靠的是虚拟机,虚拟机采用C/C++编写,适配各个系统,通过JNI为上层Java提供各种服务,保证跨平台性。下面将从Android虚拟机启动开始深入理解JNI的原理。
二、Android虚拟机启动
在Android系统systemserver启动上,有介绍过虚拟机的启动。虚拟机的启动是在Zygote进程中,Zygote启动过程中通过AndroidRuntime::start函数中的startVm来创建虚拟机而后注册JNI函数。
2.1 AR::start
1 | void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) |
2.2 AR::startVm
1 | int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool primary_zygote) |
startVM主要是对虚拟机的一些参数进行配置,配置完成之后才执行JNI_CreateJavaVM方法。这个方法会创建JavaVM,每个进程都有一个JNIEnv,虚拟机创建完成之后就可进行JNI的调用。
2.2.1 JNI_CreateJavaVM
[->java_vm_ext.cc]
1 | // JNI Invocation interface. |
Runtime表示当前进程ART虚拟机实例
2.2.2 Runtime::Create
1 | bool Runtime::Create(const RuntimeOptions& raw_options, bool ignore_unrecognized) { |
2.2.3 Runtime::Init
[->runtime.cc]
1 | bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { |
这个方法比较长,主要是对Art虚拟机进行一序列初始化。比较重要的有,创建ART虚拟机堆,创建JavaVMExt实例,启动当前线程,加载OAT文件。
2.3 startReg
1 | int AndroidRuntime::startReg(JNIEnv* env) |
2.3.1 register_jni_procs
1 | static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env) |
循环调用gRegJNI数组成员所对应的方法
1 | static const RegJNIRec gRegJNI[] = { |
上面有100多个成员变量,每个成员变量代表一个类文件的JNI映射,通过宏定义的方式调用相应的方法。
下面就一个例子,分析下注册的过程
2.3.2 register_com_android_internal_os_RuntimeInit
1 | int register_com_android_internal_os_RuntimeInit(JNIEnv* env) |
jniRegisterNativeMethods最后调用到jni.h中的RegisterNatives方法
2.3.3 RegisterNatives
[->jni.h]
1 | jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, |
functions是指向JNINativeInterface
结构体指针,也就是将调用下面方法:
1 | #if defined(__cplusplus) |
通过这种方法java层的nativeFinishInit与native层的com_android_internal_os_RuntimeInit_nativeFinishInit就完成了映射,后面通过JNIEvn这个变量就可以访问java中的方法了。
虚拟机中有两个重要的变量JavaVM和JNIEnv:
JavaVM
:进程虚拟机环境,每个进程有且只有一个JavaVM实例JNIEnv
:线程上下文环境,每个线程有且只有一个JNIEnv实例,通过该变量调用Java中的代码
三、动态库加载分析
JNI注册除了上面通过手动注册外,一般都是通过System.loadLibrary方法,下面就这个过程进行分析。
3.1 System.loadLibrary
[->System.java]
1 | public static void loadLibrary(String libname) { |
3.2 Runtime.loadLibrary0
[->Runtime.java]
1 | synchronized void loadLibrary0(ClassLoader loader, String libname) { |
这里核心的操作时nativeLoad方法,来加载so动态库,注意该方法为同步方法。
如果classload为空,则从默认mLibPaths下查看库是否存在并加载
如果classload不为空,则通过findLibrary查找库并加载
3.3 findLibrary
[->BaseDexClassLoader.java]
1 | @Override |
Classloader一般都是PathClassLoader,由于PathClassLoader继承于BaseDexClassLoader,没有复写该方法,所以调用的是BaseDexClassLoader方法。
3.3.1 DexPathList初始化
[->BaseDexClassLoader.java]
1 | public BaseDexClassLoader(String dexPath, File optimizedDirectory, |
3.3.1.1 new DexPathList
1 | DexPathList(ClassLoader definingContext, String dexPath, |
DexPathList初始化主要是给两个全局变量赋值
dexElements:记录所有的dexFile文件
nativeLibraryPathElements:记录所有的Native动态库,包括app目录下和系统目录下的native库
app目录下:/data/app/
系统目录:/system/lib下
3.3.1.2 makePathElements
1 | private static NativeLibraryElement[] makePathElements(List<File> files) { |
将native动态库存在nativeLibraryPathElements中,可以看出一个native库对应一个NativeLibraryElement
3.3.2 DexPathList.findLibrary
[->DexPathList.java]
1 | public String findLibrary(String libraryName) { |
3.3.2.1 System_mapLibraryName
[->System.c]
1 | JNIEXPORT jstring JNICALL |
这里主要是将库的名字前面加上lib后缀加so,例如库名字是native,则转化后为libnative.so
3.3.2.2 findNativeLibrary
[->DexPathList.java]
1 | public String findNativeLibrary(String name) { |
找到目标动态库,准备加载。
3.4 nativeLoad
[->libcore/ojluni/src/main/native/Runtime.c]
1 | JNIEXPORT jstring JNICALL |
3.4.1 JVM_NativeLoad
[->art/openjdkjvm/OpenjdkJvm.cc]
1 | JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env, |
3.4.2 LoadNativeLibrary
[->art/runtime/java_vm_ext.cc]
1 | bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, |
3.4.2 OpenNativeLibrary
[->art/openjdkjvm/OpenjdkJvm.cc]
1 | void* OpenNativeLibrary(JNIEnv* env, |
LoadNativeLibrar最后通过android::OpenNativeLibrary加载so库,这里有一个名字空间的概念,通过它来实现系统的私有库,不被第三方加载,这样应用就没法去链接系统的私有库。
3.5 getLibPaths()
[->Runtime.java]
1 | private String[] getLibPaths() { |
这里主要是查找系统库下的so库,/system/lib64
3.6 总结
动态库的调用顺序如下
1 | System.loadLibrary() |
加载动态库的主要流程如下:
1.判断是否为空,如果classload为空,则从默认mLibPaths下查看库是否存在并加载;如果classload不为空,则通过findLibrary查找库并加载,这两种加载库最后调用都是nativeLoad方法;
2.nativeLoad最后通过 android::OpenNativeLibrary来加载so库,通过这种方式来实现系统的私有so库,最后调用dlopen来打开动态库;
3.判断JNI_OnLoad方法是否存在,如果存在就调用该方法。
四、JNI应用
4.1 数据签名
4.1.1 基本数据类型
Signature格式 | Java | Native |
---|---|---|
B | byte | jbyte |
C | char | jchar |
D | double | jdouble |
F | float | jfloat |
I | int | jint |
S | short | jshort |
J | long | jlong |
Z | boolean | jboolean |
V | void | void |
4.1.2 数组数据类型
Signature格式 | Java | Native |
---|---|---|
[B | byte[] | jbyteArray |
[C | char[] | jcharArray |
[D | double[] | jdoubleArray |
[F | float[] | jfloatArray |
[I | int[] | jintArray |
[S | short[] | jshortArray |
[J | long[] | jlongArray |
[Z | boolean[] | jbooleanArray |
4.1.3 对象数据类型
Signature格式 | Java | Native |
---|---|---|
Ljava/lang/String; | String | jstring |
L+classname +; | 所有对象 | jobject |
[L+classname +; | Object[] | jobjectArray |
Ljava.lang.Class; | Class | jclass |
Ljava.lang.Throwable; | Throwable | jthrowable |
4.1.4 函数签名
Java函数 | 对应的签名 |
---|---|
void foo() | ()V |
float foo(int i) | (I)F |
long foo(int[] i) | ([I)J |
double foo(Class c) | (Ljava/lang/Class;)D |
boolean foo(int[] i,String s) | ([ILjava/lang/String;)Z |
String foo(int i) | (I)Ljava/lang/String; |
4.2 Native层调用Java层
4.2.1 访问属性
访问普通属性
1 | public String gBlogName = "skytoby"; |
访问静态属性
1 | public static String gStaticBlogName = "skytoby"; |
4.2.2.访问方法
每个native函数,都至少有两个参数(JNIEnv*,jobject)
1)当native方法为静态方法时:
jclass 代表native方法所属类的class对象
2)当native方法为非静态方法时:
jobject 代表native方法所属的对象
访问普通方法
1 | public String getgBlogName(int age){ |
访问静态方法
1 | public static String getStaticgBlogName(int age){ |
4.3 引用
在JNI中有三种引用关系
Local Reference(本地引用)
Global Reference(全局引用)
Weak Global Reference(全局弱引用)
Global Reference如果不主动释放,则一直不会释放;对于其他两种类型的引用都是释放的可能性。不管是这三种类型的哪种引用,在内存不再需要时,应立即释放,减少不可预知的性能与稳定性问题。
1 | env->NewGlobalRef(obj); |
4.4 异常处理
Java中点异常用trycatch就可以处理,不会影响代码点继续执行,但是JNI中的异常,Java层是无法捕获的,只能够在JNI中清除,可以通过ThrowNew给Java层抛出异常,让java层捕获。
1 | jclass clazz = env->GetObjectClass(obj); |
五、总结
本文主要从虚拟机的启动开始,再详细分析动态库的加载流程,最后对JNI的应用有详细的举例。
虚拟机启动过程中:
1.对Art虚拟机进行一序列初始化,如创建ART虚拟机堆,创建JavaVMExt实例,启动当前线程,加载OAT文件;
2.注册JNI函数,分析注册的详细过程
动态库的加载流程:
1.判断是否为空,如果classload为空,则从默认mLibPaths下查看库是否存在并加载;如果classload不为空,则通过findLibrary查找库并加载,这两种加载库最后调用都是nativeLoad方法;
2.nativeLoad最后通过 android::OpenNativeLibrary来加载so库,通过这种方式来实现系统的私有so库,最后调用dlopen来打开动态库;
3.判断JNI_OnLoad方法是否存在,如果存在就调用该方法。
JNI应用主要介绍了数据签名,Nativie层调用java层的基本方法,以及JNI引用和异常处理的问题。
附录
1 | /libcore/ojluni/src/main/java/java/lang/System.java |